/* SPI Master example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"

#include "esp_log.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>


#include <sys/param.h>

#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "tcpip_adapter.h"

#include "esp_timer.h"
#include "esp_event_loop.h"

/*
 This code displays some fancy graphics on the 320x240 LCD on an ESP-WROVER_KIT board.
 This example demonstrates the use of both spi_device_transmit as well as
 spi_device_queue_trans/spi_device_get_trans_result and pre-transmit callbacks.

 Some info about the ILI9341/ST7789V: It has an C/D line, which is connected to a GPIO here. It expects this
 line to be low for a command and high for data. We use a pre-transmit callback here to control that
 line: every transaction has as the user-definable argument the needed state of the D/C line and just
 before the transaction is sent, the callback will set this line to the correct state.
*/

#define PIN_NUM_MISO 25
#define PIN_NUM_MOSI 18
#define PIN_NUM_CLK  19
#define PIN_NUM_CS   22

#define PIN_NUM_DC   4
#define PIN_NUM_RST  5
#define PIN_NUM_BCKL 2

#define PIN_NUM_LED  21

//To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. More means more memory use,
//but less overhead for setting up / finishing transfers. Make sure 240 is dividable by this.
#define PARALLEL_LINES 24

static const char *TAG="MAIN";

/*
 The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
*/
typedef struct {
    uint8_t cmd;
    uint8_t data[16];
    uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;

typedef enum {
    LCD_TYPE_ILI = 1,
    LCD_TYPE_ST,
    LCD_TYPE_MAX,
} type_lcd_t;

//Place data into DRAM. Constant data gets placed into DROM by default, which is not accessible by DMA.
DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[]={
    /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */
    {0x36, {(1<<5)|(1<<6)}, 1},
    /* Interface Pixel Format, 16bits/pixel for RGB/MCU interface */
	{0x3A, {0x55}, 1},
    /* Porch Setting */
    {0xB2, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5},
    /* Gate Control, Vgh=13.65V, Vgl=-10.43V */
	{0xB7, {0x35}, 1},
#if !CONFIG_LCD_COLOR_BIG_ENDIAN
	/* Little Endian (LSB first) */
	{0xB0, {0x00, 0xC8}, 2},
#endif /* CONFIG_LCD_COLOR_BIG_ENDIAN */
    /* VCOM Setting, VCOM=1.175V */
	{0xBB, {0x19}, 1},
    /* LCM Control, XOR: BGR, MX, MH */
    {0xC0, {0x2C}, 1},
    /* VDV and VRH Command Enable, enable=1 */
	{0xC2, {0x01}, 1},
    /* VRH Set, Vap=4.4+... */
	{0xC3, {0x12}, 1},
    /* VDV Set, VDV=0 */
    {0xC4, {0x20}, 1},
    /* Frame Rate Control, 60Hz, inversion=0 */
    {0xC6, {0x0f}, 1},
    /* Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V */
    {0xD0, {0xA4, 0xA1}, 1},
    /* Positive Voltage Gamma Control */
	{0xE0, {0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23}, 14},
    /* Negative Voltage Gamma Control */
	{0xE1, {0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23}, 14},
	{0x21, {0}, 0x80},
    /* Sleep Out */
    {0x11, {0}, 0x80},
    /* Display On */
    {0x29, {0}, 0x80},
    {0, {0}, 0xff}
};

/* Send a command to the LCD. Uses spi_device_polling_transmit, which waits
 * until the transfer is complete.
 *
 * Since command transactions are usually small, they are handled in polling
 * mode for higher speed. The overhead of interrupt transactions is more than
 * just waiting for the transaction to complete.
 */
void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=8;                     //Command is 8 bits
    t.tx_buffer=&cmd;               //The data is the cmd itself
    t.user=(void*)0;                //D/C needs to be set to 0
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

/* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the
 * transfer is complete.
 *
 * Since data transactions are usually small, they are handled in polling
 * mode for higher speed. The overhead of interrupt transactions is more than
 * just waiting for the transaction to complete.
 */
void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len)
{
    esp_err_t ret;
    spi_transaction_t t;
    if (len==0) return;             //no need to send anything
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=len*8;                 //Len is in bytes, transaction length is in bits.
    t.tx_buffer=data;               //Data
    t.user=(void*)1;                //D/C needs to be set to 1
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

//This function is called (in irq context!) just before a transmission starts. It will
//set the D/C line to the value indicated in the user field.
void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
{
    int dc=(int)t->user;
    gpio_set_level(PIN_NUM_DC, dc);
}

uint32_t lcd_get_id(spi_device_handle_t spi)
{
    //get_id cmd
    lcd_cmd(spi, 0x04);

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=8*3;
    t.flags = SPI_TRANS_USE_RXDATA;
    t.user = (void*)1;

    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );

    return *(uint32_t*)t.rx_data;
}

//Initialize the display
void lcd_init(spi_device_handle_t spi)
{
    int cmd=0;
    const lcd_init_cmd_t* lcd_init_cmds;

    //Initialize non-SPI GPIOs
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);

    //Reset the display
    gpio_set_level(PIN_NUM_RST, 0);
    vTaskDelay(100 / portTICK_RATE_MS);
    gpio_set_level(PIN_NUM_RST, 1);
    vTaskDelay(100 / portTICK_RATE_MS);

    //detect LCD type
//    uint32_t lcd_id = lcd_get_id(spi);

    printf("LCD ST7789V initialization.\n");
    lcd_init_cmds = st_init_cmds;

    //Send all the commands
    while (lcd_init_cmds[cmd].databytes!=0xff) {
        lcd_cmd(spi, lcd_init_cmds[cmd].cmd);
        lcd_data(spi, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F);
        if (lcd_init_cmds[cmd].databytes&0x80) {
            vTaskDelay(100 / portTICK_RATE_MS);
        }
        cmd++;
    }
}

//void lcd_draw_pixel(spi_device_handle_t spi, int xpos, int ypos, uint16_t data)
//{
//    static uint8_t buff[4];
//    buff[2] = buff[0] = xpos>>8;
//    buff[3] = buff[1] = xpos & 0xff;
//    lcd_cmd(spi, 0x2A);
//    lcd_data(spi, buff, 4);
//    buff[2] = buff[0] = ypos>>8;
//    buff[3] = buff[1] = ypos & 0xff;
//    lcd_cmd(spi, 0x2B);
//    lcd_data(spi, buff, 4);
//    lcd_cmd(spi, 0x2C);
//    buff[0] = data&0xff;
//    buff[1] = data>>8;
//    lcd_data(spi, buff, 2);
//    //When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
//    //mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
//    //finish because we may as well spend the time calculating the next line. When that is done, we can call
//    //send_line_finish, which will wait for the transfers to be done and check their status.
//}

//static void lcd_spi_finish(spi_device_handle_t spi)
//{
//    spi_transaction_t *rtrans;
//    esp_err_t ret;
//    //Wait for all 6 transactions to be done and get back the results.
//    for (int x = 0; x < 6; x ++) {
//        ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
//        assert(ret==ESP_OK);
//        //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
//    }
//}
//uint8_t spi_trans_ck = 0;
//void lcd_set_area(spi_device_handle_t spi, int left, int right, int top, int bottom)
//{
//  esp_err_t ret;
//  static spi_transaction_t trans[5];
//  int x;
//  for(x = 0; x < 5; x ++) {
//    memset(&trans[x], 0, sizeof(spi_transaction_t));
//    if ((x & 1) == 0) {
//      //Even transfers are commands
//      trans[x].length = 8;
//      trans[x].user = (void*)0;
//    } else {
//      //Odd transfers are data
//      trans[x].length=8*4;
//      trans[x].user=(void*)1;
//    }
//    trans[x].flags = SPI_TRANS_USE_TXDATA;
//  }
//  trans[0].tx_data[0] = 0x2A;           //Column Address Set
//  trans[1].tx_data[0] = left >> 8;      //Start Col High
//  trans[1].tx_data[1] = left & 0xFF;    //Start Col Low
//  trans[1].tx_data[2] = right >> 8;     //End Col High
//  trans[1].tx_data[3] = right & 0xFF;   //End Col Low
//  trans[2].tx_data[0] = 0x2B;           //Page address set
//  trans[3].tx_data[0] = top >> 8;       //Start page high
//  trans[3].tx_data[1] = top & 0xFF;     //start page low
//  trans[3].tx_data[2] = bottom >> 8;    //end page high
//  trans[3].tx_data[3] = bottom & 0xFF;  //end page low
//  trans[4].tx_data[0] = 0x2C;           //memory write
//
//  if(spi_trans_ck)
//    lcd_spi_finish(spi);
//  else spi_trans_ck = 1;
//  //Queue all transactions.
//  for (x = 0; x < 5; x ++) {
//    ret = spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
//    assert(ret == ESP_OK);
//  }
//}
//
//void lcd_fill_data(spi_device_handle_t spi, uint16_t *data, uint32_t len)
//{
//  esp_err_t ret;
//  static spi_transaction_t trans;
//  memset(&trans, 0, sizeof(spi_transaction_t));
//  trans.tx_buffer = data;
//  trans.length = len * 2 * 8; //Data length, in bits
//  trans.flags = 0;
//  trans.user = (void*)1;
//  ret = spi_device_queue_trans(spi, &trans, portMAX_DELAY);
//  assert(ret == ESP_OK);
//}

#include "decode_image.h"

/* To send a set of lines we have to send a command, 2 data bytes, another command, 2 more data bytes and another command
 * before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction
 * because the D/C line needs to be toggled in the middle.)
 * This routine queues these commands up as interrupt transactions so they get
 * sent faster (compared to calling spi_device_transmit several times), and at
 * the mean while the lines for next transactions can get calculated.
 */
static void send_lines(spi_device_handle_t spi, int ypos, uint16_t *linedata)
{
  esp_err_t ret;
  int x;

  //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
  //function is finished because the SPI driver needs access to it even while we're already calculating the next line.
  static spi_transaction_t trans[6];

  //In theory, it's better to initialize trans and data only once and hang on to the initialized
  //variables. We allocate them on the stack, so we need to re-init them each call.
  for (x = 0; x < 6; x ++) {
    memset(&trans[x], 0, sizeof(spi_transaction_t));
    if ((x&1) == 0) {
      //Even transfers are commands
      trans[x].length = 8;
      trans[x].user = (void *)0;
    } else {
      //Odd transfers are data
      trans[x].length = 8 * 4;
      trans[x].user = (void *)1;
    }
    trans[x].flags = SPI_TRANS_USE_TXDATA;
  }
  trans[0].tx_data[0] = 0x2A;                        //Column Address Set
  trans[1].tx_data[0] = 0;                           //Start Col High
  trans[1].tx_data[1] = 0;                           //Start Col Low
  trans[1].tx_data[2] = (320)>>8;                    //End Col High
  trans[1].tx_data[3] = (320)&0xff;                  //End Col Low
  trans[2].tx_data[0] = 0x2B;                        //Page address set
  trans[3].tx_data[0] = ypos>>8;                     //Start page high
  trans[3].tx_data[1] = ypos&0xff;                   //start page low
  trans[3].tx_data[2] = (ypos+PARALLEL_LINES)>>8;    //end page high
  trans[3].tx_data[3] = (ypos+PARALLEL_LINES)&0xff;  //end page low
  trans[4].tx_data[0] = 0x2C;                        //memory write
  trans[5].tx_buffer = linedata;                     //finally send the line data
  trans[5].length = 320 * 2 * 8 * PARALLEL_LINES;    //Data length, in bits
  trans[5].flags = 0;                                //undo SPI_TRANS_USE_TXDATA flag

  //Queue all transactions.
  for (x = 0; x < 6; x ++) {
    ret = spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
    assert(ret == ESP_OK);
  }

  //When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
  //mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
  //finish because we may as well spend the time calculating the next line. When that is done, we can call
  //send_line_finish, which will wait for the transfers to be done and check their status.
}

static void send_line_finish(spi_device_handle_t spi)
{
  spi_transaction_t *rtrans;
  esp_err_t ret;
  //Wait for all 6 transactions to be done and get back the results.
  for (int x=0; x<6; x++) {
    ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
    assert(ret==ESP_OK);
    //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
  }
}
uint16_t *lines[2];
//Simple routine to generate some patterns and send them to the LCD. Don't expect anything too
//impressive. Because the SPI driver handles transactions in the background, we can calculate the next line
//while the previous one is being sent.
static void display_pretty_colors(spi_device_handle_t spi)
{
  //Indexes of the line currently being sent to the LCD and the line we're calculating.
  int sending_line = -1;
  int calc_line = 0;

  for (int y = 0; y < 240; y += PARALLEL_LINES) {
    //Calculate a line.
    decode_get_lines(lines[calc_line], y, PARALLEL_LINES);
    //Finish up the sending process of the previous line, if any
    if (sending_line != -1) send_line_finish(spi);
    //Swap sending_line and calc_line
    sending_line = calc_line;
    calc_line ^= 1;
    //Send the line we currently calculated.
    send_lines(spi, y, lines[sending_line]);
    //The line set is queued up for sending now; the actual sending happens in the
    //background. We can go on to calculate the next line set as long as we do not
    //touch line[sending_line]; the SPI sending process is still reading from that.
  }
  send_line_finish(spi); // finish the sending process
}

uint8_t jpg_cache_1[10240];
uint8_t jpg_cache_2[10240];
uint8_t jpg_buf_id = 0;

const uint32_t RECV_READY_BIT = BIT0;
const uint32_t DISP_READY_BIT = BIT1;
static EventGroupHandle_t disp_evt_group;
static void img_decode_task(void *pvParameters)
{
  uint8_t *jpg_buffer[2];
  uint8_t disp_buff_id = 0;
  jpg_buffer[0] = jpg_cache_1;
  jpg_buffer[1] = jpg_cache_2;
  spi_device_handle_t spi = *(spi_device_handle_t *)pvParameters;
//  uint32_t fps = 0;
  for(;;) {
    xEventGroupWaitBits(disp_evt_group, RECV_READY_BIT, true, true, portMAX_DELAY);
    disp_buff_id = jpg_buf_id ^ 1;
    decode_image((uint8_t *)jpg_buffer[disp_buff_id]);
    display_pretty_colors(spi);

//    fps ++;
//    gpio_set_level(PIN_NUM_LED, fps % 2);
    xEventGroupSetBits(disp_evt_group, DISP_READY_BIT);
  }
}

#define HOST_IP_ADDR "192.168.4.1"
#define PORT 3333

static void tcp_client_task(void *pvParameters)
{
  size_t len = 0;
  struct img_hdr {
    uint8_t hdr[3];
    size_t len;
  } img_header;
//  spi_device_handle_t spi = *(spi_device_handle_t *)pvParameters;
  int addr_family;
  int ip_protocol;

  uint8_t *jpg_buffer[2];
  jpg_buffer[0] = jpg_cache_1;
  jpg_buffer[1] = jpg_cache_2;

  disp_evt_group = xEventGroupCreate();
  xEventGroupSetBits(disp_evt_group, DISP_READY_BIT);
  xEventGroupClearBits(disp_evt_group, RECV_READY_BIT);

  while (1) {
    struct sockaddr_in dest_addr;
    dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(PORT);
    addr_family = AF_INET;
    ip_protocol = IPPROTO_IP;
//     inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);

    int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
    if (sock < 0) {
      ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
      break;
    }
    ESP_LOGI(TAG, "Socket created, connecting to %s:%d", HOST_IP_ADDR, PORT);

    int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err != 0) {
      ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
      break;
    }

    xTaskCreatePinnedToCore(img_decode_task, "img_decode", 2048, pvParameters, 5, NULL, 1);
    ESP_LOGI(TAG, "Successfully connected");

    while (1) {
      int err = send(sock, "\xFFREQ", 4, 0); // send request
      if (err < 0) { // Error occurred during sending
        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
        break;
      }

      err = recv(sock, &img_header, sizeof(struct img_hdr), 0); // read header
      if (err < 0) { // Error occurred during receiving
        ESP_LOGE(TAG, "recv failed: errno %d", errno);
        break;
      }

      if(img_header.hdr[0] == 'i' && img_header.hdr[1] == 'm' && img_header.hdr[2] == 'g') { // check header
//        ESP_LOGI(TAG, "size:%u", img_header.len);
        len = 0;
        do {
          err = recv(sock, (char *)jpg_buffer[jpg_buf_id] + len, img_header.len - len, 0);
          if (err < 0) {
            ESP_LOGE(TAG, "recv failed: errno %d", errno);
            goto reset;
          }
          len += err;
        } while(img_header.len > len);
        jpg_buffer[jpg_buf_id][img_header.len] = 0;// Null-terminate whatever we received and treat like a string
        xEventGroupWaitBits(disp_evt_group, DISP_READY_BIT, true, true, portMAX_DELAY); // wait displayer ready
        jpg_buf_id ^= 1;
        xEventGroupSetBits(disp_evt_group, RECV_READY_BIT); // flag data received
      } else {
        // flush cache
        do {
          err = recv(sock, (char *)jpg_buffer[jpg_buf_id], 10240, 0);
        } while(err > 0);
      }
//      vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
reset:
    if (sock != -1) {
      ESP_LOGE(TAG, "Shutting down socket and restarting...");
      shutdown(sock, 0);
      close(sock);
    }
  }
  vTaskDelete(NULL);
}

//#define DEFAULT_SCAN_LIST_SIZE 5
//static void print_auth_mode(int authmode)
//{
//    switch (authmode) {
//    case WIFI_AUTH_OPEN:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_OPEN");
//        break;
//    case WIFI_AUTH_WEP:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WEP");
//        break;
//    case WIFI_AUTH_WPA_PSK:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA_PSK");
//        break;
//    case WIFI_AUTH_WPA2_PSK:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA2_PSK");
//        break;
//    case WIFI_AUTH_WPA_WPA2_PSK:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA_WPA2_PSK");
//        break;
//    case WIFI_AUTH_WPA2_ENTERPRISE:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA2_ENTERPRISE");
//        break;
//    case WIFI_AUTH_WPA3_PSK:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA3_PSK");
//        break;
//    case WIFI_AUTH_WPA2_WPA3_PSK:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_WPA2_WPA3_PSK");
//        break;
//    default:
//        ESP_LOGI(TAG, "Authmode \tWIFI_AUTH_UNKNOWN");
//        break;
//    }
//}
//
//static void print_cipher_type(int pairwise_cipher, int group_cipher)
//{
//    switch (pairwise_cipher) {
//    case WIFI_CIPHER_TYPE_NONE:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_NONE");
//        break;
//    case WIFI_CIPHER_TYPE_WEP40:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_WEP40");
//        break;
//    case WIFI_CIPHER_TYPE_WEP104:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_WEP104");
//        break;
//    case WIFI_CIPHER_TYPE_TKIP:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_TKIP");
//        break;
//    case WIFI_CIPHER_TYPE_CCMP:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_CCMP");
//        break;
//    case WIFI_CIPHER_TYPE_TKIP_CCMP:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_TKIP_CCMP");
//        break;
//    default:
//        ESP_LOGI(TAG, "Pairwise Cipher \tWIFI_CIPHER_TYPE_UNKNOWN");
//        break;
//    }
//
//    switch (group_cipher) {
//    case WIFI_CIPHER_TYPE_NONE:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_NONE");
//        break;
//    case WIFI_CIPHER_TYPE_WEP40:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_WEP40");
//        break;
//    case WIFI_CIPHER_TYPE_WEP104:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_WEP104");
//        break;
//    case WIFI_CIPHER_TYPE_TKIP:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_TKIP");
//        break;
//    case WIFI_CIPHER_TYPE_CCMP:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_CCMP");
//        break;
//    case WIFI_CIPHER_TYPE_TKIP_CCMP:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_TKIP_CCMP");
//        break;
//    default:
//        ESP_LOGI(TAG, "Group Cipher \tWIFI_CIPHER_TYPE_UNKNOWN");
//        break;
//    }
//}
//
///* Initialise a wifi_ap_record_t, get it populated and display scanned data */
//static void display_scan_result(void)
//{
//    uint16_t number = DEFAULT_SCAN_LIST_SIZE;
//    wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
//    uint16_t ap_count = 0;
//    memset(ap_info, 0, sizeof(ap_info));
//
//    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
//    ESP_LOGI(TAG, "Total APs scanned = %u", ap_count);
//    int i = 0;
//    do {
//      ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
//      for (i = 0; (i < DEFAULT_SCAN_LIST_SIZE) && (i < ap_count); i ++) {
//        ESP_LOGI(TAG, "SSID \t\t%s", ap_info[i].ssid);
//        ESP_LOGI(TAG, "RSSI \t\t%d", ap_info[i].rssi);
//        print_auth_mode(ap_info[i].authmode);
//        if (ap_info[i].authmode != WIFI_AUTH_WEP) {
//          print_cipher_type(ap_info[i].pairwise_cipher, ap_info[i].group_cipher);
//        }
//        ESP_LOGI(TAG, "Channel \t\t%d\n", ap_info[i].primary);
//      }
//      ap_count -= i;
//    } while(ap_count > 0);
//}

#define ESP_WIFI_SSID     "kyChuCam"
#define ESP_WIFI_PASS     ""
#define ESP_MAXIMUM_RETRY 5

/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
//static ip4_addr_t s_ip_addr;
//const int CONNECTED_BIT = BIT0;
/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static int s_retry_num = 0;

//static bool scan_done = false;

//static esp_err_t event_handler(void* ctx, system_event_t* event)
static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data)
{
//  if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
//    scan_done = true;
//  } else
	  if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
    esp_wifi_connect();
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
//    if (s_retry_num < ESP_MAXIMUM_RETRY) {
      esp_wifi_connect();
//      s_retry_num ++;
//      ESP_LOGI(TAG, "retry to connect to the AP");
//    } else {
//      xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
//      ESP_LOGI(TAG,"connect to the AP fail");
//    }
  } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
    ip_event_got_ip_t* event = (ip_event_got_ip_t *) event_data;
    ESP_LOGI(TAG, "got ip:%s",
    ip4addr_ntoa(&event->ip_info.ip));
    s_retry_num = 0;
    xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
  }
}

static void wifi_init_sta()
{
  s_wifi_event_group = xEventGroupCreate();

  tcpip_adapter_init();

  ESP_ERROR_CHECK(esp_event_loop_create_default());
//  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));

  ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
  ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

  wifi_config_t wifi_config = {
    .sta = {
      .ssid = ESP_WIFI_SSID,
      .password = ESP_WIFI_PASS,
      .pmf_cfg = {
        .capable = true,
        .required = false
      },
    },
  };

  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
  ESP_ERROR_CHECK(esp_wifi_start());

//  ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, true));

//  int cnt = 0;
//  while (1) {
//    if (scan_done == true) {
//          display_scan_result();
//          scan_done = false;
//      } else {
//          vTaskDelay(100 / portTICK_RATE_MS);
//          gpio_set_level(PIN_NUM_LED, cnt ++ % 2);
//          continue;
//      }
//  }

  ESP_LOGI(TAG, "wifi_init_sta finished.");

  /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
   * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
  EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                         WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                         pdFALSE,
                                         pdFALSE,
                                         portMAX_DELAY);

  /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
   * happened. */
  if (bits & WIFI_CONNECTED_BIT) {
    ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
             ESP_WIFI_SSID, ESP_WIFI_PASS);
  } else if (bits & WIFI_FAIL_BIT) {
    ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
             ESP_WIFI_SSID, ESP_WIFI_PASS);
  } else {
    ESP_LOGE(TAG, "UNEXPECTED EVENT");
  }

  ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
  ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
  vEventGroupDelete(s_wifi_event_group);
}

//Reference the binary-included jpeg file
extern const uint8_t logo_jpg_start[]   asm("_binary_logo_jpg_start");
extern const uint8_t logo_jpg_end[]     asm("_binary_logo_jpg_end");

void app_main()
{
  esp_err_t ret;
  ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK( nvs_flash_erase() );
    ESP_ERROR_CHECK( nvs_flash_init() );
  }

  spi_device_handle_t spi;
  spi_bus_config_t buscfg = {
    .miso_io_num = PIN_NUM_MISO,
    .mosi_io_num = PIN_NUM_MOSI,
    .sclk_io_num = PIN_NUM_CLK,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = PARALLEL_LINES * 320 * 2 + 8
  };
  spi_device_interface_config_t devcfg = {
#ifdef CONFIG_LCD_OVERCLOCK
    .clock_speed_hz = 26*1000*1000,           //Clock out at 26 MHz
#else
    .clock_speed_hz = 10*1000*1000,           //Clock out at 10 MHz
#endif
    .mode = 0,                                //SPI mode 0
    .spics_io_num = PIN_NUM_CS,               //CS pin
    .queue_size = 7,                          //We want to be able to queue 7 transactions at a time
    .pre_cb = lcd_spi_pre_transfer_callback,  //Specify pre-transfer callback to handle D/C line
  };
  //Initialize the SPI bus
  ret = spi_bus_initialize(VSPI_HOST, &buscfg, 1);
  ESP_ERROR_CHECK(ret);
  //Attach the LCD to the SPI bus
  ret = spi_bus_add_device(VSPI_HOST, &devcfg, &spi);
  ESP_ERROR_CHECK(ret);
  //Initialize the LCD
  lcd_init(spi);
  //Initialize the LED
  gpio_pad_select_gpio(PIN_NUM_LED);
  /* Set the GPIO as a push/pull output */
  gpio_set_direction(PIN_NUM_LED, GPIO_MODE_OUTPUT);
  //Initialize the effect displayed
  decoder_init();
  //Allocate memory for the pixel buffers
  for (int i = 0; i < 2; i ++) {
    lines[i] = heap_caps_malloc(320 * PARALLEL_LINES * sizeof(uint16_t), MALLOC_CAP_DMA);
    assert(lines[i] != NULL);
  }
  decode_image(logo_jpg_start);
  display_pretty_colors(spi);
  // Enable backlight
  gpio_set_level(PIN_NUM_BCKL, 1);

  wifi_init_sta();

//  ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, true));

//  xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT, true, true, portMAX_DELAY);
  xTaskCreatePinnedToCore(tcp_client_task, "tcp_client", 4096, (void *)&spi, 5, NULL, 0);
}
